//
// Created by A4-28 on 2020/9/30.
//

#include <glad/glad.h>
#include "../camera/camera.h"
#include "../material/Shader.h"
#include "../utils/stb_image.h"
#include "../utils/Utils.h"
#include "../material/Material.h"
#include <vector>
#include <map>

float cubeVertices[] = {
        // positions          // texture Coords
        -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
        0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,

        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
        -0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,

        -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,

        -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
        -0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
float planeVertices[] = {
        // positions          // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
        5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
        -5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,

        5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
        5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
float transparentVertices[] = {
        // positions         // texture Coords (swapped y coordinates because texture is flipped upside down)
        0.0f, 0.5f, 0.0f, 0.0f, 0.0f,
        0.0f, -0.5f, 0.0f, 0.0f, 1.0f,
        1.0f, -0.5f, 0.0f, 1.0f, 1.0f,

        0.0f, 0.5f, 0.0f, 0.0f, 0.0f,
        1.0f, -0.5f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.5f, 0.0f, 1.0f, 0.0f
};

Utils *utilss = new Utils();
GlfwInit *init = new GlfwInit();
Camera camera1(
        glm::vec3(0, 0, 3.0f),
        glm::radians(0.0f),
        glm::radians(180.0f),
        glm::vec3(0, 1.0f, 0),
        0.0005,
        0.0005,
        45.0);

void camera1Move(GLFWwindow *w, double xPos, double yPos) {
    camera1.Movement(xPos, yPos);
}

void handleInput(GLFWwindow *w) {
    if (glfwGetKey(w, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(w, true);
    }

    if (glfwGetKey(w, GLFW_KEY_A) == GLFW_PRESS) {
        camera1.SpeedX = -1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_D) == GLFW_PRESS) {
        camera1.SpeedX = 1.0f;
    } else {
        camera1.SpeedX = 0.0f;
    }

    if (glfwGetKey(w, GLFW_KEY_W) == GLFW_PRESS) {
        camera1.SpeedZ = 1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_S) == GLFW_PRESS) {
        camera1.SpeedZ = -1.0f;
    } else {
        camera1.SpeedZ = 0.0f;
    }

    if (glfwGetKey(w, GLFW_KEY_Q) == GLFW_PRESS) {
        camera1.SpeedY = 1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_E) == GLFW_PRESS) {
        camera1.SpeedY = -1.0f;
    } else {
        camera1.SpeedY = 0.0f;
    }
}

void framebuffer_size_callback(GLFWwindow *window, int width, int height) {
    glViewport(0, 0, width, height);
}

int main() {
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow *window = glfwCreateWindow(500, 500, "blending", NULL, NULL);
    if (window == NULL) {
        printf("create window error");
        glfwTerminate();
        return 1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, camera1Move);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        printf("load glad error");
        glfwTerminate();
        return 1;
    }
    glViewport(0, 0, 500, 500);
    glEnable(GL_DEPTH_TEST);
    //虽然直接丢弃片段很好，但它不能让我们渲染半透明的图像。我们要么渲染一个片段，要么完全丢弃它。
    // 要想渲染有多个透明度级别的图像，我们需要启用混合(Blending)。
    /**
     * 混合方程式
       C¯result=C¯source∗Fsource+C¯destination∗Fdestination
        C¯source：源颜色向量。这是源自纹理的颜色向量。
        C¯destination：目标颜色向量。这是当前储存在颜色缓冲中的颜色向量。
        Fsource：源因子值。指定了alpha值对源颜色的影响。
        Fdestination：目标因子值。指定了alpha值对目标颜色的影响。

        在的测试都通过之后，这个混合方程(Blend Equation)才会应用到片段颜色输出与当前颜色缓冲中的值（当前片段之前储存的之前片段的颜色）上
        源因子和目标因子的值可以由我们来决定
     */
    glEnable(GL_BLEND);
    /*
     * 通过这个函数来控制源因子和目标因子
        GL_ZERO	因子等于0
        GL_ONE	因子等于1
        GL_SRC_COLOR	因子等于源颜色向量C¯source
        GL_ONE_MINUS_SRC_COLOR	因子等于1−C¯source
        GL_DST_COLOR	因子等于目标颜色向量C¯destination
        GL_ONE_MINUS_DST_COLOR	因子等于1−C¯destination
        GL_SRC_ALPHA	因子等于C¯source的alpha分量
        GL_ONE_MINUS_SRC_ALPHA	因子等于1− C¯source的alpha分量
        GL_DST_ALPHA	因子等于C¯destination的alpha分量
        GL_ONE_MINUS_DST_ALPHA	因子等于1− C¯destination的alpha分量
        GL_CONSTANT_COLOR	因子等于常数颜色向量C¯constant
        GL_ONE_MINUS_CONSTANT_COLOR	因子等于1−C¯constant
        GL_CONSTANT_ALPHA	因子等于C¯constant的alpha分量
        GL_ONE_MINUS_CONSTANT_ALPHA	因子等于1− C¯constant的alpha分量

        C¯constant可以通过glBlendColor函数来设置常数颜色
     */
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // RGB和alpha通道分别设置不同的选项
//    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

/**
 * 当深度和混合同时开启时会出现:
 *      深度缓冲不会检查片段是否是透明的，所以透明的部分会和其它值一样写入到深度缓冲中。结果就是窗户的整个四边形不论透明度都会进行深度测试。
 *      即使透明的部分应该显示背后的窗户，深度测试仍然丢弃了它们.
 *   解决方法：要想让混合在多个物体上工作，我们需要最先绘制最远的物体，最后绘制最近的物体
 *      1.先绘制所有不透明的物体。
        2.对所有透明的物体排序。
        3.按顺序绘制所有透明的物体。从远到近
        更高级的技术还有次序无关透明度(Order Independent Transparency, OIT)
 */
#pragma region

    Shader shader = Shader();
    unsigned int ID = shader.loadShader(utilss->GetResourceDir().append("\\resource\\blending\\vertex.glsl"),
                                        utilss->GetResourceDir().append(
                                                "\\resource\\blending\\frag.glsl"));
    // cube VAO
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    // plane VAO
    unsigned int planeVAO, planeVBO;
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);
    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    // transparent VAO
    unsigned int transparentVAO, transparentVBO;
    glGenVertexArrays(1, &transparentVAO);
    glGenBuffers(1, &transparentVBO);
    glBindVertexArray(transparentVAO);
    glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    glBindVertexArray(0);

    unsigned int cubeTexture = utilss->loadTexture(utilss->GetResourceDir().append("\\resource\\marble.jpg").c_str());
    unsigned int floorTexture = utilss->loadTexture(utilss->GetResourceDir().append("\\resource\\metal.png").c_str());
    unsigned int transparentTexture = utilss->loadTexture(
            utilss->GetResourceDir().append("\\resource\\window.png").c_str());

    vector<glm::vec3> windows
            {
                    glm::vec3(-1.5f, 0.0f, -0.48f),
                    glm::vec3( 1.5f, 0.0f, 0.51f),
                    glm::vec3( 0.0f, 0.0f, 0.7f),
                    glm::vec3(-0.3f, 0.0f, -2.3f),
                    glm::vec3( 0.5f, 0.0f, -0.6f)
            };
    glUniform1i(glGetUniformLocation(ID, "ourTexcoord"), GL_TEXTURE0);

    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 project;
#pragma endregion


    while (!glfwWindowShouldClose(window)) {


        handleInput(window);

        //算出 物体到摄像机的距离
        std::map<float, glm::vec3> sorted;
        for (unsigned int i = 0; i < windows.size(); i++)
        {
            float distance = glm::length(camera1.Position - windows[i]);
            sorted[distance] = windows[i];
        }

        glClearColor(0.3, 0.3, 0.3, 1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // draw objects
        model = glm::mat4(1.0f);
        view = camera1.GetViewMatrix();
        project = glm::perspective(glm::radians(camera1.FOV), (float) 800 / (float) 800, 0.1f, 100.0f);
        glUseProgram(ID);
        glUniformMatrix4fv(glGetUniformLocation(ID, "view"), 1, false, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(ID, "project"), 1, false, glm::value_ptr(project));

        // cubes
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        // floor
        glBindVertexArray(planeVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4(1.0f);
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // vegetation
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, transparentTexture);

        //以逆序（从远到近）进行渲染
        for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it)
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, it->second);
            glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

        glfwPollEvents();
        glfwSwapBuffers(window);
        camera1.UpdateCameraPosition();
    }

    glfwTerminate();
    return 1;

}